package com.silverhaze.diamond.server.service.upgrade;

/**
 * This code is part of Kg.Axion project. Treat it as strictly confidential.
 * Created by Ivan Babanin.
 * Creation date: 01.10.11 20:22
 */

import com.silverhaze.commons.utils.collections.CollectionsUtils;
import com.silverhaze.commons.utils.io.IOStreamUtils;
import com.silverhaze.diamond.common.constants.DiamondConstants;
import com.silverhaze.diamond.common.entity.Property;
import org.jboss.ejb3.annotation.Management;
import org.jboss.ejb3.annotation.Service;

import javax.annotation.Resource;
import javax.ejb.Stateful;
import javax.ejb.Stateless;
import javax.ejb.TransactionManagement;
import javax.ejb.TransactionManagementType;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.PersistenceUnit;
import javax.persistence.Query;
import javax.transaction.*;
import java.io.InputStream;
import java.util.*;

import org.jboss.xnio.log.Logger;

/**
 * User: Babanin
 * Date: 16.12.10
 * Time: 22:11
 */
@Management(DatabaseUpgradeServiceBean.class)
@TransactionManagement(TransactionManagementType.BEAN)
@Service(name = DiamondConstants.JNDI_DOMAIN + "DatabaseUpgradeService", objectName = DiamondConstants.JMX_DOMAIN + "DatabaseUpgradeService")
public class DatabaseUpgradeServiceBean {
    private static final String[] FUNCTIONS_SQL = CollectionsUtils.array("functions/common.sql", "functions/component.sql");

    @Resource()
    private UserTransaction utx;

    @PersistenceUnit(name = DiamondConstants.JTA_PERSISTENCE_UNIT_NAME)
    private EntityManagerFactory entityManagerFactory;

    private Map<Integer, UpgradePoint> upgrages;

    public void start() throws Exception {
        injectSQLFunctions();

        initializeUpgrades();
        performUpgrade();
    }

    private void injectSQLFunctions() {
        for (String filePath : FUNCTIONS_SQL) {
            Logger.getLogger(DatabaseUpgradeServiceBean.class).info("Inject sql functions: " + filePath);
            InputStream inputStream = DatabaseUpgradeServiceBean.class.getResourceAsStream(filePath);

            if (inputStream != null) {
                try {
                    String sqlFunctions = IOStreamUtils.toString(inputStream);
                    EntityManager entityManager = entityManagerFactory.createEntityManager();

                    utx.begin();
                    entityManager.joinTransaction();
                    entityManager.createNativeQuery(sqlFunctions).executeUpdate();
                    utx.commit();

                    entityManager.close();
                } catch (Exception e) {
                    Logger.getLogger(DatabaseUpgradeServiceBean.class).error("Exception while updating database functions", e);
                }
            } else {
                Logger.getLogger(DatabaseUpgradeServiceBean.class).error("Missing " + filePath);
            }
        }
    }

    private void initializeUpgrades() {
        upgrages = new LinkedHashMap<Integer, UpgradePoint>();

        upgrages.put(1, new UpgradePoint() {
            @Override
            public void upgrade(EntityManager entityManager, UserTransaction userTransaction) throws SystemException, RollbackException, HeuristicMixedException, HeuristicRollbackException, NotSupportedException {
                Property orderIdProperty = new Property();
                orderIdProperty.setKey(Property.ORDER_ID_PROPERTY);
                orderIdProperty.setVal("0");

                entityManager.persist(orderIdProperty);
            }
        });
    }

    private void performUpgrade() throws SystemException {
        int initialDatabaseVersion = 0;
        boolean runOnlyMandatory = false;
        Property currentDatabaseVersionProperty = getCurrentDatabaseVersionProperty();

        if (currentDatabaseVersionProperty == null) {
            currentDatabaseVersionProperty = new Property();
            currentDatabaseVersionProperty.setKey(Property.DATABASE_VERSION_PROPERTY);
            runOnlyMandatory = true;
        } else {
            try {
                initialDatabaseVersion = Integer.parseInt(currentDatabaseVersionProperty.getVal());
            } catch (NumberFormatException e) {
                initialDatabaseVersion = 0;
            }
        }

        boolean stopUpgrading = false;
        int currentDatabaseVersion = initialDatabaseVersion;
        for (Map.Entry<Integer, UpgradePoint> upgradePointEntry : upgrages.entrySet()) {
            if (currentDatabaseVersion < upgradePointEntry.getKey()) {
                long upgradeDuration = System.currentTimeMillis();
                EntityManager entityManager = entityManagerFactory.createEntityManager();

                String upgradeDescription = upgradePointEntry.getValue().getDescription();

                UpgradePoint.log("Start upgrading to v. " + upgradePointEntry.getKey() +
                        (!upgradeDescription.isEmpty() ? " (" + upgradeDescription + ")" : ""));

                try {
                    utx.begin();
                    entityManager.joinTransaction();

                    if (runOnlyMandatory) {
                        if (upgradePointEntry.getValue().isMandatory()) {
                            upgradePointEntry.getValue().upgrade(entityManager, utx);
                        }
                    } else {
                        upgradePointEntry.getValue().upgrade(entityManager, utx);
                    }

                    // update database version
                    currentDatabaseVersion = upgradePointEntry.getKey();
                    saveCurrentDatabaseVersionProperty(currentDatabaseVersion);

                    utx.commit();

                    upgradeDuration = System.currentTimeMillis() - upgradeDuration;
                    UpgradePoint.log("Upgrade to v. " + currentDatabaseVersion + " completed successfully [" +
                            (upgradeDuration / 1000) + " s.]");
                } catch (Exception e) {
                    UpgradePoint.error("Failed to perform upgrade #" + upgradePointEntry.getKey() + ". Transaction failed: ", e);
                    stopUpgrading = true;
                    utx.rollback();
                } finally {
                    entityManager.close();
                }
            }

            if (stopUpgrading) {
                break;
            }
        }

        if (currentDatabaseVersion == initialDatabaseVersion) {
            System.out.println("No upgrade required. Current version #" + currentDatabaseVersion);
        }
    }

    private boolean safeExecuteQuery(EntityManager entityManager, String query) {
        try {
            entityManager.createNativeQuery(query).executeUpdate();
            return true;
        } catch (Exception e) {
            e.printStackTrace();
        }

        return false;
    }

    private Property getCurrentDatabaseVersionProperty() {
        Query query = entityManagerFactory.createEntityManager().createQuery("SELECT p FROM Property p WHERE p.key = :key").
                setParameter("key", Property.DATABASE_VERSION_PROPERTY);

        List resultList = query.getResultList();
        if (resultList.size() > 0) {
            return (Property) resultList.get(0);
        }

        return null;
    }

    private void saveCurrentDatabaseVersionProperty(int version) {
        entityManagerFactory.createEntityManager().
                createQuery("UPDATE Property p SET p.value = :value WHERE p.key = :key").
                setParameter("value", "" + version).
                setParameter("key", Property.DATABASE_VERSION_PROPERTY).executeUpdate();
    }
}